﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.WindowsAzure.StorageClient;

namespace Ogdi.Data.DataLoader
{
    public class TableEntity : TableServiceEntity
    {
        private Entity _entity;
        
        public TableEntity() : base() 
        { 
        }

        public TableEntity(Entity entity)
            : this(entity, entity.Id.ToString(), string.Empty)
        {
        }

        public TableEntity(Entity entity, string partitionKey, string rowKey)
            : base(partitionKey, rowKey)
        {
            _entity = entity;
        }

        public IEnumerable<NameTypeValueTuple> GetProperties()
        {
            foreach (var e in _entity)
            {
                if (IsValidPropertyValue(e.Value))
                {
                    yield return new NameTypeValueTuple()
                    {
                        Name = ConvertToValidPropertyName(e.Name),
                        Type = ConvertClrTypeToEdmType(e.Value.GetType()),
                        Value = e.Value
                    };
                }
            }
        }

        public override string ToString()
        {
            var sb = new StringBuilder();
            sb.Append("\nTableEntity Start:\n");
            sb.AppendFormat("\t'PartitionKey '{0}'\n", this.PartitionKey);
            sb.AppendFormat("\t'RowKey '{0}'\n", this.RowKey);
            foreach (var p in this.GetProperties())
            {
                sb.AppendFormat("\t'{0}' '{1}' '{2}'\n", p.Name, p.Type, p.Value);
            }

            sb.Append("TableEntity End\n");
            return sb.ToString();
        }

        private static bool IsValidPropertyValue(object p)
        {
            var isValid = true;

            if (p.GetType() == typeof(DateTime))
            {
                var d = (DateTime)p;
                if (d < new DateTime(1600, 1, 1) || d > new DateTime(9999, 12, 31))
                {
                    isValid = false;
                }
            }

            return isValid;
        }

        private static string ConvertClrTypeToEdmType(Type clrType)
        {
            if (clrType == typeof(string))
            {
                return null;
            }
            else if (clrType == typeof(Int32))
            {
                return "Edm.Int32";
            }
            else if (clrType == typeof(Int64))
            {
                return "Edm.Int64";
            }
            else if (clrType == typeof(double))
            {
                return "Edm.Double";
            }
            else if (clrType == typeof(bool))
            {
                return "Edm.Boolean";
            }
            else if (clrType == typeof(Guid))
            {
                return "Edm.Guid";
            }
            else if (clrType == typeof(DateTime))
            {
                return "Edm.DateTime";
            }
            else
            {
                throw new ArgumentException(string.Format(DataLoaderConstants.MsgUnsupportedClrType, clrType));
            }
        }

        private static string ConvertToValidPropertyName(string name)
        {
            var nameIn = name.ToLower();
            var nameOut = new StringBuilder();
            for (int i = 0; i < nameIn.Length; ++i)
            {
                if (char.IsLetterOrDigit(nameIn[i]) || nameIn[i] == '-' || nameIn[i] == '_')
                {
                    nameOut.Append(nameIn[i]);
                }
            }

            if (char.IsLetter(nameOut[0]))
            {
                return nameOut.ToString();
            }
            else
            {
                return nameOut.Insert(0, 'a').ToString();
            }
        }

        public class NameTypeValueTuple
        {
            public string Name { get; set; }

            public string Type { get; set; }

            public object Value { get; set; }
        }


        public void SetEntity(Entity entity)
        {
            this._entity = entity;
        }

        /*
         * Used when we want to load entity from storage and update its columns except EntityID
         */
        public void UpdateEntity(Entity from)
        {
            foreach (var prop in from)
            {
                if (_entity[prop.Name] == null)
                    _entity.AddProperty(prop.Name, prop.Value);
                else if (prop.Name.ToLower() != DataLoaderConstants.PropNameEntityId.ToLower())
                    _entity[prop.Name] = prop.Value;
            }
        }

        public void UpdateProperty(string name, object value)
        {
            if (_entity[name] == null)
                _entity.AddProperty(name, value);
            else
                _entity[name] = value;
        }


        /*
         * Check if property values are the same (except ExceptionColumns)
         * Returns null if values are same
         */
        public string FindDifferences(Entity to, string[] ExceptionColumns)
        {
            string result = "";
            List<Property> newProps = new List<Property>();
            DateTime oldLastUpdateDate = DateTime.MinValue;
            DateTime newLastUpdateDate = DateTime.MinValue;
            //get only properties with values != null
            foreach (var prop in to)
            {
                if (IsBelongTo(prop.Name, ExceptionColumns))
                    continue;
                if (prop.Value == null)
                    continue;
                if (prop.Value is DateTime && (DateTime)prop.Value == DateTime.MinValue) //MinDate is like null
                    continue;
                newProps.Add(prop);
            }
            List<Property> oldProps = new List<Property>();
            foreach (var prop in _entity)//we have only properties with values != null when loading from storage, so don't need to check for null
            {
                if (IsBelongTo(prop.Name, ExceptionColumns))
                    continue;
                oldProps.Add(prop);
            }

            //check that columns equal
            if (oldProps.Count() != newProps.Count())
            {
                string oldColumns = "";
                foreach (var prop in oldProps)
                    oldColumns += prop.Name + "; ";
                string newColumns = "";
                foreach (var prop in newProps)
                    newColumns += prop.Name + "; ";
                result += "Different columns: " + System.Environment.NewLine;
                result += "existing columns: " + oldColumns + System.Environment.NewLine;
                result += "new columns: " + newColumns + System.Environment.NewLine;
                return result;
            }
            //check values
            foreach (var prop in oldProps)
            {
                string oldValue = prop.Value == null ? "null" : prop.Value.ToString();
                string newValue = to[prop.Name] == null ? "null" : to[prop.Name].ToString();
                if (oldValue != newValue)
                {
                    result += prop.Name + " : existing = " + oldValue + " : new = " + newValue + System.Environment.NewLine;
                }
            }
            if (result == "")
                result = null;
            return result;
            
        }

        //some util function
        private static bool IsBelongTo(string value, string[] set)
        {
            if (String.IsNullOrEmpty(value) || set == null)
                return false;
            foreach(var st in set)
            {
                if (st != null && value.ToLower() == st.ToLower())
                    return true;
            }
            return false;
        }

    }
}
